package com.sipgate.siplistener;

import com.sipgate.siplistener.processor.RequestProcessor;
import gov.nist.core.NameValue;
import gov.nist.javax.sdp.SessionDescriptionImpl;
import gov.nist.javax.sdp.TimeDescriptionImpl;
import gov.nist.javax.sdp.fields.*;
import gov.nist.javax.sip.SipStackExt;
import gov.nist.javax.sip.clientauthutils.AccountManager;
import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
import gov.nist.javax.sip.header.ContentType;
import org.apache.log4j.Logger;

import javax.sdp.*;
import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.*;

public class SipgateSipListener implements SipListener {
    private SipStack          sipStack;
    private SipProvider       sipProvider;
    private ClientTransaction registerTid;
    private HeaderFactory     headerFactory;
    private AddressFactory    addressFactory;
    private MessageFactory    messageFactory;
    private Dialog            dialog;

    private       ListeningPoint         lp;
    private final AccountManager         accountManager = new SipgateAccountManager();
    private final SipgateUserCredentials credentials;
    private       ServerTransaction      inviteTid;

    private       String           localIPAddress = "192.168.0.234";
    private final RequestProcessor requestProcessor;

    private final static Logger log = Logger.getLogger(SipgateSipListener.class);

    static class MyTimerTask extends TimerTask {
        SipgateSipListener listener;

        public MyTimerTask(SipgateSipListener listener) {
            this.listener = listener;

        }

        public void run() {
            log.debug("ReInvite please");
            try {
                listener.register();
            } catch(Exception e) {
                e.printStackTrace();
            }
        }

    }

    public SipgateSipListener() throws PeerUnavailableException {
        requestProcessor = new RequestProcessor();
        credentials = (SipgateUserCredentials) accountManager.getCredentials(null, null);
    }

    public void init() throws Exception {
        SipFactory sipFactory = SipFactory.getInstance();

        Properties properties = new Properties();
        properties.setProperty("javax.sip.IP_ADDRESS", getLocalIPAddress());
        properties.setProperty("javax.sip.OUTBOUND_PROXY", credentials.getProxy() + "/" + ListeningPoint.UDP);
        properties.setProperty("javax.sip.STACK_NAME", "Sip Test");
        properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "16");
//		properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "shootmedebug.txt");
//		properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "shootmelog.txt");

        // Create SipStack object
        sipStack = sipFactory.createSipStack(properties);
        headerFactory = sipFactory.createHeaderFactory();
        addressFactory = sipFactory.createAddressFactory();
        messageFactory = sipFactory.createMessageFactory();
        lp = sipStack.createListeningPoint(getLocalIPAddress(), 8002, ListeningPoint.UDP);

        SipgateSipListener listener = this;

        sipProvider = sipStack.createSipProvider(lp);
        sipProvider.addSipListener(listener);
    }

    public void register() throws ParseException, InvalidArgumentException, SipException {
        // create >From Header
        SipURI fromAddress = addressFactory.createSipURI(credentials.getUserName(), getLocalIPAddress());

        Address fromNameAddress = addressFactory.createAddress(fromAddress);
        fromNameAddress.setDisplayName(credentials.getDisplayName());
        FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, null);

        // create To Header
        SipURI  toAddress     = addressFactory.createSipURI(credentials.getUserName(), credentials.getSipDomain());
        Address toNameAddress = addressFactory.createAddress(toAddress);
        toNameAddress.setDisplayName(credentials.getDisplayName());
        ToHeader toHeader = headerFactory.createToHeader(toNameAddress, null);

        // create Request URI
        SipURI requestURI = addressFactory.createSipURI(credentials.getUserName(), credentials.getSipDomain());

        // Create ViaHeaders

        List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
        String          ipAddress  = lp.getIPAddress();
        ViaHeader       viaHeader  = headerFactory.createViaHeader(ipAddress, lp.getPort(), lp.getTransport(), null);

        // add via headers
        viaHeaders.add(viaHeader);


        // Create a new CallId header
        CallIdHeader callIdHeader = sipProvider.getNewCallId();

        // Create a new Cseq header
        CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L, Request.REGISTER);

        // Create a new MaxForwardsHeader
        MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);

        // Create the request.
        Request request = messageFactory.createRequest(requestURI, Request.REGISTER, callIdHeader, cSeqHeader, fromHeader,
                                                       toHeader, viaHeaders, maxForwards);

        // Create contact headers
        SipURI contactUrl = addressFactory.createSipURI(credentials.getUserName(), getLocalIPAddress());
        contactUrl.setPort(8002);
        contactUrl.setLrParam();

        // Create the contact name address.
        SipURI contactURI = addressFactory.createSipURI(credentials.getUserName(), getLocalIPAddress());
        contactURI.setPort(sipProvider.getListeningPoint(lp.getTransport()).getPort());

        Address contactAddress = addressFactory.createAddress(contactURI);

        // Add the contact address.
        contactAddress.setDisplayName(credentials.getDisplayName());

        ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
        contactHeader.setParameter("expires", "5");
        request.addHeader(contactHeader);

        // Create UserAgentHeader
        ArrayList<String> productList = new ArrayList<String>(1);
        productList.add("SipListenerSample");
        UserAgentHeader userAgentHeader = headerFactory.createUserAgentHeader(productList);
        request.addHeader(userAgentHeader);

        // Create the client transaction.
        registerTid = sipProvider.getNewClientTransaction(request);
//        log.debug("send : \n" + registerTid.getRequest());
        // send the request out.
        registerTid.sendRequest();

        dialog = registerTid.getDialog();
    }

    public void invite() throws ParseException, InvalidArgumentException, SipException {
        // create >From Header
        SipURI fromAddress = addressFactory.createSipURI("13832156663", getLocalIPAddress());

        Address fromNameAddress = addressFactory.createAddress(fromAddress);
        fromNameAddress.setDisplayName(credentials.getDisplayName());
        FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, UUID.randomUUID().toString().replaceAll("-", ""));

        // create To Header
        SipURI   toAddress     = addressFactory.createSipURI("88888888", credentials.getSipDomain());
        Address  toNameAddress = addressFactory.createAddress(toAddress);
        ToHeader toHeader      = headerFactory.createToHeader(toNameAddress, null);

        // create Request URI
        SipURI requestURI = addressFactory.createSipURI("88888888", credentials.getSipDomain());

        // Create ViaHeaders
        List<ViaHeader> viaHeaders = new ArrayList<>();
        String          ipAddress  = lp.getIPAddress();
        ViaHeader       viaHeader  = headerFactory.createViaHeader(ipAddress, lp.getPort(), lp.getTransport(), null);

        // add via headers
        viaHeaders.add(viaHeader);

        // Create a new CallId header
        CallIdHeader callIdHeader = sipProvider.getNewCallId();
        // Create a new Cseq header
        CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L, Request.INVITE);
        // Create a new MaxForwardsHeader
        MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);
        // Create the request.
        Request request = messageFactory.createRequest(requestURI, Request.INVITE, callIdHeader, cSeqHeader, fromHeader,
                                                       toHeader, viaHeaders, maxForwards);
        // Create contact headers
        SipURI contactUrl = addressFactory.createSipURI(credentials.getUserName(), getLocalIPAddress());
        contactUrl.setPort(8002);
        contactUrl.setLrParam();
        // Create the contact name address.
        SipURI contactURI = addressFactory.createSipURI(credentials.getUserName(), getLocalIPAddress());
        contactURI.setPort(sipProvider.getListeningPoint(lp.getTransport()).getPort());
        Address contactAddress = addressFactory.createAddress(contactURI);
        // Add the contact address.
        contactAddress.setDisplayName("13832156663");
        ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
        request.addHeader(contactHeader);
        // Create UserAgentHeader
        ArrayList<String> productList = new ArrayList<String>(1);
        productList.add("SipListenerSample");
        UserAgentHeader userAgentHeader = headerFactory.createUserAgentHeader(productList);
        request.addHeader(userAgentHeader);

        AllowHeader allowHeader = headerFactory.createAllowHeader("INVITE, ACK, CANCEL, BYE, OPTIONS, INFO, UPDATE, PRACK");
        request.addHeader(allowHeader);
        SupportedHeader supportedHeader = headerFactory.createSupportedHeader("timer, 100rel");
        request.addHeader(supportedHeader);

        SessionDescriptionImpl sessionDescriptionImpl = new SessionDescriptionImpl();
        try {
            ProtoVersionField ProtoVersionField = new ProtoVersionField();
            ProtoVersionField.setVersion(0);
            sessionDescriptionImpl.setVersion(ProtoVersionField);
            OriginField originImpl = new OriginField();
            originImpl.setUsername("-");
            originImpl.setSessId(47326L);
            originImpl.setSessVersion(47326L);
            originImpl.setNettype("IN");
            originImpl.setAddressType("IP4");
            originImpl.setAddress("192.168.0.234");
            sessionDescriptionImpl.setOrigin(originImpl);

            SessionNameField sessionNameImpl = new SessionNameField();
            sessionNameImpl.setValue("sipListener");
            sessionDescriptionImpl.setSessionName(sessionNameImpl);

            ConnectionField connectionField = new ConnectionField();
            connectionField.setNettype(Connection.IN);
            connectionField.setAddrType(Connection.IP4);
            connectionField.setAddress("192.168.0.234");
            sessionDescriptionImpl.setConnection(connectionField);

            TimeField timeField = new TimeField();
            timeField.setZero();
            sessionDescriptionImpl.addField(timeField);

            MediaField mediaField = new MediaField();
            mediaField.setMedia("audio");
            mediaField.setPort(46158);
            mediaField.setProto(SdpConstants.RTP_AVP);
            Vector<Integer> mediaFormat = new Vector<>();
            mediaFormat.add(8);
            mediaFormat.add(0);
            mediaFormat.add(108);
            mediaFormat.add(4);
            mediaFormat.add(101);
            mediaField.setFormats(mediaFormat);
            sessionDescriptionImpl.addField(mediaField);

            AttributeField af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "8 PCMA/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "0 PCMU/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "108 AMR/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("fmtp", "108 mode-set=7"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "18 G729/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("fmtp", "18 annexb=no"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "4 G723/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "101 telephone-event/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("fmtp", "101 0-15"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("ptime", "20"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setValue("sendrecv");
            sessionDescriptionImpl.addField(af);

            ContentType contentType = new ContentType();
            contentType.setContentType("application");
            contentType.setContentSubType("sdp");
            String sdp = sessionDescriptionImpl.toString();
            request.setContent(sdp, contentType);
        } catch(SdpException e) {
            e.printStackTrace();
        }

        // Create the client transaction.
        registerTid = sipProvider.getNewClientTransaction(request);
//        log.debug("send : \n" + registerTid.getRequest());
        // send the request out.
        registerTid.sendRequest();
        dialog = registerTid.getDialog();
    }

    public void update(CallIdHeader callId, long remoteSeqNumber, String fromTag, String toTag) throws ParseException, InvalidArgumentException, SipException {
        // create >From Header
        SipURI fromAddress = addressFactory.createSipURI("13832156663", getLocalIPAddress());

        Address fromNameAddress = addressFactory.createAddress(fromAddress);
        fromNameAddress.setDisplayName(credentials.getDisplayName());
        FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, fromTag);

        // create To Header
        SipURI   toAddress     = addressFactory.createSipURI("88888888", credentials.getSipDomain());
        Address  toNameAddress = addressFactory.createAddress(toAddress);
        ToHeader toHeader      = headerFactory.createToHeader(toNameAddress, toTag);

        // create Request URI
        SipURI requestURI = addressFactory.createSipURI("88888888", credentials.getSipDomain());

        // Create ViaHeaders
        List<ViaHeader> viaHeaders = new ArrayList<>();
        String          ipAddress  = lp.getIPAddress();
        ViaHeader       viaHeader  = headerFactory.createViaHeader(ipAddress, lp.getPort(), lp.getTransport(), null);

        // add via headers
        viaHeaders.add(viaHeader);

        // Create a new CallId header
        CallIdHeader callIdHeader = callId;
        // Create a new Cseq header
        CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(remoteSeqNumber, Request.UPDATE);
        // Create a new MaxForwardsHeader
        MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);
        // Create the request.
        Request request = messageFactory.createRequest(requestURI, Request.UPDATE, callIdHeader, cSeqHeader, fromHeader,
                                                       toHeader, viaHeaders, maxForwards);
        // Create contact headers
        SipURI contactUrl = addressFactory.createSipURI(credentials.getUserName(), getLocalIPAddress());
        contactUrl.setPort(8002);
        contactUrl.setLrParam();
        // Create the contact name address.
        SipURI contactURI = addressFactory.createSipURI(credentials.getUserName(), getLocalIPAddress());
        contactURI.setPort(sipProvider.getListeningPoint(lp.getTransport()).getPort());
        Address contactAddress = addressFactory.createAddress(contactURI);
        // Add the contact address.
        contactAddress.setDisplayName("13832156663");
        ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
        request.addHeader(contactHeader);
        // Create UserAgentHeader
        ArrayList<String> productList = new ArrayList<String>(1);
        productList.add("SipListenerSample");
        UserAgentHeader userAgentHeader = headerFactory.createUserAgentHeader(productList);
        request.addHeader(userAgentHeader);

        AllowHeader allowHeader = headerFactory.createAllowHeader("INVITE, ACK, CANCEL, BYE, OPTIONS, INFO, UPDATE, PRACK");
        request.addHeader(allowHeader);
        SupportedHeader supportedHeader = headerFactory.createSupportedHeader("timer, 100rel");
        request.addHeader(supportedHeader);

        SessionDescriptionImpl sessionDescriptionImpl = new SessionDescriptionImpl();
        try {
            ProtoVersionField ProtoVersionField = new ProtoVersionField();
            ProtoVersionField.setVersion(0);
            sessionDescriptionImpl.setVersion(ProtoVersionField);
            OriginField originImpl = new OriginField();
            originImpl.setUsername("-");
            originImpl.setSessId(47326L);
            originImpl.setSessVersion(47326L);
            originImpl.setNettype("IN");
            originImpl.setAddressType("IP4");
            originImpl.setAddress("192.168.0.234");
            sessionDescriptionImpl.setOrigin(originImpl);

            SessionNameField sessionNameImpl = new SessionNameField();
            sessionNameImpl.setValue("sipListener");
            sessionDescriptionImpl.setSessionName(sessionNameImpl);

            ConnectionField connectionField = new ConnectionField();
            connectionField.setNettype(Connection.IN);
            connectionField.setAddrType(Connection.IP4);
            connectionField.setAddress("192.168.0.234");
            sessionDescriptionImpl.setConnection(connectionField);

            TimeField timeField = new TimeField();
            timeField.setZero();
            sessionDescriptionImpl.addField(timeField);

            MediaField mediaField = new MediaField();
            mediaField.setMedia("audio");
            mediaField.setPort(46158);
            mediaField.setProto(SdpConstants.RTP_AVP);
            Vector<Integer> mediaFormat = new Vector<>();
            mediaFormat.add(8);
            mediaFormat.add(0);
            mediaFormat.add(108);
            mediaFormat.add(4);
            mediaFormat.add(101);
            mediaField.setFormats(mediaFormat);
            sessionDescriptionImpl.addField(mediaField);

            AttributeField af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "8 PCMA/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "0 PCMU/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "108 AMR/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("fmtp", "108 mode-set=7"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "18 G729/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("fmtp", "18 annexb=no"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "4 G723/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("rtpmap", "101 telephone-event/8000"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("fmtp", "101 0-15"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setAttribute(new NameValue("ptime", "20"));
            sessionDescriptionImpl.addField(af);

            af = new AttributeField();
            af.setValue("sendrecv");
            sessionDescriptionImpl.addField(af);

            ContentType contentType = new ContentType();
            contentType.setContentType("application");
            contentType.setContentSubType("sdp");
            String sdp = sessionDescriptionImpl.toString();
            request.setContent(sdp, contentType);
        } catch(SdpException e) {
            e.printStackTrace();
        }

        // Create the client transaction.
        registerTid = sipProvider.getNewClientTransaction(request);
//        log.debug("send : \n" + registerTid.getRequest());
        // send the request out.
        registerTid.sendRequest();
    }

    public String getLocalIPAddress() {
        if(localIPAddress != null) {
            return localIPAddress;
        }

        InetAddress internetAddress;
        try {
            internetAddress = InetAddress.getLocalHost();
        } catch(UnknownHostException e) {
            e.printStackTrace();
            return "127.0.0.1";
        }
        StringBuilder address = new StringBuilder();
        byte[]        bytes   = internetAddress.getAddress();
        for(int j = 0; j < bytes.length; j++) {
            int i = bytes[j] < 0 ? bytes[j] + 256 : bytes[j];
            address.append(i);
            if(j < 3)
                address.append('.');
        }

        localIPAddress = address.toString();

        log.debug("getLocalIp return: " + localIPAddress);
        return localIPAddress;
    }

    public void processRequest(RequestEvent requestReceivedEvent) {
        try {
            requestProcessor.process(requestReceivedEvent, dialog);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public void processResponse(ResponseEvent responseReceivedEvent) {
//        log.debug("response : \n" + responseReceivedEvent.getResponse());
        Response          response = responseReceivedEvent.getResponse();
        ClientTransaction tid      = responseReceivedEvent.getClientTransaction();
        CSeqHeader        cseq     = (CSeqHeader) response.getHeader(CSeqHeader.NAME);

        if(response.getStatusCode() == Response.UNAUTHORIZED
           || response.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) {
            AuthenticationHelper authHelper = ((SipStackExt) sipStack).getAuthenticationHelper(accountManager, headerFactory);
            try {
                registerTid = authHelper.handleChallenge(response, tid, sipProvider, -1);
//                log.debug("send : \n" + registerTid.getRequest());
                registerTid.sendRequest();
            } catch(SipException e1) {
                e1.printStackTrace();
            }
        }

        try {
            switch(response.getStatusCode()) {
                case Response.OK:
                    switch(cseq.getMethod()) {
                        case Request.REGISTER:
                            ContactHeader contactHeader = (ContactHeader) response.getHeader("Contact");
                            long seconds = contactHeader.getExpires();
                            long delay = (seconds * 1000) - 100;
                            new Timer().schedule(new MyTimerTask(this), delay);
                            log.debug("Okay. We are registered for next " + seconds + " seconds.");
                            break;
                        case Request.INVITE:
                            Request ackRequest = dialog.createAck(cseq.getSeqNumber());
                            log.debug("INVITE 响应，回复 ACK");
                            dialog.sendAck(ackRequest);

                            log.debug("INVITE 响应 183，回复 UPDATE");
                            update(dialog.getCallId(), dialog.getLocalSeqNumber(), dialog.getLocalTag(), dialog.getRemoteTag());

                            break;
                        case Request.CANCEL:
                            if(dialog.getState() == DialogState.CONFIRMED) {
                                // oops cancel went in too late. Need to hang up the dialog.
                                log.debug("Sending BYE -- cancel went in too late !!");
                                Request           byeRequest = dialog.createRequest(Request.BYE);
                                ClientTransaction ct         = sipProvider.getNewClientTransaction(byeRequest);
                                dialog.sendRequest(ct);
                            }
                            break;
                    }
                    break;
                case Response.SESSION_PROGRESS:
                    switch(cseq.getMethod()) {
                        case Request.INVITE:
                            log.debug("INVITE 响应 183，回复 UPDATE");
                            update(dialog.getCallId(), dialog.getLocalSeqNumber(), dialog.getLocalTag(), dialog.getRemoteTag());
                            break;
                        case Request.CANCEL:
                            if(dialog.getState() == DialogState.CONFIRMED) {
                                // oops cancel went in too late. Need to hang up the dialog.
                                log.debug("Sending BYE -- cancel went in too late !!");
                                Request           byeRequest = dialog.createRequest(Request.BYE);
                                ClientTransaction ct         = sipProvider.getNewClientTransaction(byeRequest);
                                dialog.sendRequest(ct);
                            }
                            break;
                    }
                    break;
                default:
                    log.debug("未处理的状态：" + response.getStatusCode());
            }

        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public void processTimeout(TimeoutEvent arg0) {
        log.debug("Process event recieved.");
    }

    public void processTransactionTerminated(TransactionTerminatedEvent arg0) {
        log.debug("Transaction terminated event recieved.");
    }

    public void processDialogTerminated(DialogTerminatedEvent arg0) {
        log.debug("Dialog terminated event recieved.");
    }

    public void processIOException(IOExceptionEvent arg0) {
        log.debug("IO Exception event recieved.");
    }
}
